package flare.scale {
	import flare.util.Dates;
	import flare.util.Maths;

	/**
	 * Scale for timelines represented using <code>Date</code> values. This
	 * scale represents a linear, quantitative time line. The class attempts
	 * to automatically configure date value labels based on the time span
	 * between the earliest and latest date in the scale. The label formatting
	 * pattern can also be manually set using the <code>labelFormat</code>
	 * property.
	 */
	public class TimeScale extends Scale
	{
		private var _dmin:Date = new Date(0);
		private var _dmax:Date = new Date(0);
		private var _smin:Date;
		private var _smax:Date;
		private var _autofmt:Boolean = true;
		
		/**
		 * Creates a new TimeScale.
		 * @param min the minimum (earliest) date value
		 * @param max the maximum (latest) date value
		 * @param flush the flush flag for scale padding
		 * @param labelFormat the formatting pattern for value labels
		 */
		public function TimeScale(min:Date=null, max:Date=null,
			flush:Boolean=false, labelFormat:String=null)
		{
			if (min) this.dataMin = min;
			if (max) this.dataMax = max;
			this.flush = flush;
			this.labelFormat = labelFormat;
		}
		
		/** @inheritDoc */
		public override function get scaleType():String {
			return ScaleType.TIME;
		}
		
		/** @inheritDoc */
		public override function clone():Scale {
			return new TimeScale(_dmin, _dmax, _flush, _format);
		}
		
		// -- Properties ------------------------------------------------------
		
		/** @inheritDoc */
		public override function set flush(val:Boolean):void
		{
			_flush = val; updateScale();
		}
		
		/** @inheritDoc */
		public override function get labelFormat():String
		{
			return (_autofmt ? null : super.labelFormat);
		}
		
		public override function set labelFormat(fmt:String):void
		{
			if (fmt != null) {
				super.labelFormat = fmt;
				_autofmt = false;
			} else {
				_autofmt = true;
				updateScale();
			}
		}
		
		/** @inheritDoc */
		public override function get min():Object { return dataMin; }
		public override function set min(o:Object):void { dataMin = o as Date; }
		
		/** @inheritDoc */
		public override function get max():Object { return dataMax; }
		public override function set max(o:Object):void { dataMax = o as Date; }
		
		/** The minimum (earliest) Date value in the underlying data.
		 *  This property is the same as the <code>minimum</code>
		 *  property, but properly typed. */
		public function get dataMin():Date
		{
			return _dmin;
		}
		public function set dataMin(val:Date):void
		{
			_dmin = val; updateScale();
		}

		/** The maximum (latest) Date value in the underlying data.
		 *  This property is the same as the <code>maximum</code>
		 *  property, but properly typed. */
		public function get dataMax():Date
		{
			return _dmax;
		}
		public function set dataMax(val:Date):void
		{
			_dmax = val; updateScale();
		}
		
		/** The minimum (earliest) Date value in the scale. */
		public function get scaleMin():Date
		{
			return _smin;
		}
		
		/** The maximum (latest) Date value in the underlying data. */
		public function get scaleMax():Date
		{
			return _smax;
		}
		
		// -- Scale Methods ---------------------------------------------------
		
		/** @inheritDoc */
		public override function interpolate(value:Object):Number
		{
			var t:Number = value is Date ? (value as Date).time : Number(value);
			return Maths.invLinearInterp(t, _smin.time, _smax.time);
		}
		
		/** @inheritDoc */
		public override function lookup(f:Number):Object
		{
			var t:Number = Math.round(Maths.linearInterp(f, _smin.time, _smax.time));
			return new Date(t);
		}
		
		/**
		 * Updates the scale range when the data range is changed.
		 */
		protected function updateScale():void
		{
			var span:int = Dates.timeSpan(_dmin, _dmax);
			if (_flush) {
				_smin = _dmin;
				_smax = _dmax;
			} else {
				_smin = Dates.roundTime(_dmin, span, false);
				_smax = Dates.roundTime(_dmax, span, true);
			}
			if (_autofmt) {
				super.labelFormat = formatString(span);
			}
		}
		
		/**
		 * Determines the format string to be used based on a measure of
		 * the time span covered by this scale.
		 * @param span the time span covered by this scale. Should use the
		 *  format of the <code>flare.util.Dates</code> class.
		 * @return the label formatting pattern
		 */
		protected function formatString(span:int):String
		{
			if (span >= Dates.YEARS) {
				return "yyyy";
			} else if (span == Dates.MONTHS) {
				return "MMM";
			} else if (span == Dates.DAYS) {
				return "d";
			} else if (span == Dates.HOURS) {
				return "h:mmt";
			} else if (span == Dates.MINUTES) {
				return "h:mmt";
			} else if (span == Dates.SECONDS) {
				return "h:mm:ss";
			} else {
				return "s.fff";
			}
		}
		
		/** @inheritDoc */
		public override function values(num:int=-1):Vector.<Object>
		{   
            var a:Vector.<Object> = new Vector.<Object>();
            var span:int = Dates.timeSpan(_dmin, _dmax);
            var step:Number = Dates.timeStep(span);
			var max:Number = _smax.time;
            var d:Date = _flush ? Dates.roundTime(scaleMin, span, true) : scaleMin;

            if (span < Dates.MONTHS) {
            	for (var x:Number = _smin.time; x <= max; x += step) {
            		a.push(new Date(x));
            	}
            } else if (span == Dates.MONTHS) {
            	for (; d.time <= max; d = Dates.addMonths(d,1)) {
            		a.push(d);
            	}
            } else {
            	var y:int = int(step);
            	for (; d.time <= max; d = Dates.addYears(d,y)) {
            		a.push(d);
            	}
            }
			return a;
		}
		
	} // end of class TimeScale
}